﻿#include "NetMapSocket.h"
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>	
#include <poll.h>
#include "common.h"

#ifdef __cplusplus
extern "C" {
#endif

#include "libnetmap.h"

#ifdef __cplusplus
}

#endif


NetMapSocket::NetMapSocket(const std::string& ifname)
{
    std::string _ifname = GetIfByMac(ifname);
    if ("" == _ifname) throw std::runtime_error("get if error");

    std::string name = "netmap:" + _ifname;
    m_nmd = nmport_prepare(name.c_str());
    if (NULL == m_nmd) throw std::runtime_error("nmport_prepare error : " + name);

    if (nmport_open_desc(m_nmd) < 0)
    {
        nmport_undo_prepare(m_nmd);
        m_nmd = NULL;

        throw std::runtime_error("nmport_open_desc error : " + name);
    }
    LOGFMTI("mapped %luKB at %p ", (unsigned long)(m_nmd->reg.nr_memsize >> 10), m_nmd->mem);
}

NetMapSocket::~NetMapSocket()
{
    struct netmap_if* nifp = NULL;
    struct netmap_ring* txring = NULL;

    if (m_nmd)
    {
        nifp = m_nmd->nifp;
        for (int i = m_nmd->first_tx_ring; i <= m_nmd->last_tx_ring; i++)
        {
            txring = NETMAP_TXRING(nifp, i);
            while (nm_tx_pending(txring))
            {
                LOGFMTI("pending tx tail %d head %d on ring %d", txring->tail, txring->head, i);
                ioctl(m_nmd->fd, NIOCTXSYNC, NULL);
                usleep(1); /* wait 1 tick */
            }
        }

        nmport_undo_open_desc(m_nmd);
        nmport_undo_prepare(m_nmd);
    }
    m_nmd = NULL;
}

int NetMapSocket::SendData(DataCache& data, int timeout)
{
    struct pollfd pfd = { .fd = m_nmd->fd, .events = POLLOUT };
    struct netmap_if* nifp = NULL;
    struct netmap_ring* txring = NULL;
    uint64_t sent = 0, _sent = 0;

    int tosend = 0, rv, i;
    u_int bufsz, frag_size = data.size(), frags = 0;

    nifp = m_nmd->nifp;
    txring = NETMAP_TXRING(nifp, m_nmd->first_tx_ring);
    bufsz = txring->nr_buf_size; /* netmap buff size */

    if (bufsz < frag_size)
    {
        frag_size = bufsz;
        frags = data.size() / frag_size;
        if (data.size() % frag_size != 0) frags++;
    }
    else
        frags = 1;

    /*
     * wait for available room in the send queue(s)
     */
#ifdef BUSYWAIT
    (void)rv;
    if (ioctl(pfd.fd, NIOCTXSYNC, NULL) < 0) {
        LOGFMTI("ioctl error on queue: %s", strerror(errno));
        retuern - 1;
    }
#else
    if ((rv = poll(&pfd, 1, timeout)) <= 0) {
        LOGFMTI("poll error on queue: %s", rv ? strerror(errno) : "timeout");
        return -2;
    }
    if (pfd.revents & POLLERR) {
        LOGFMTI("poll error on %d ring %d-%d", pfd.fd, m_nmd->first_tx_ring, m_nmd->last_tx_ring);
        return -1;
    }
#endif
    /*
     * scan our queues and send on those with room
     */
    for (i = m_nmd->first_tx_ring; i <= m_nmd->last_tx_ring && sent < frags; i++)
    {
        txring = NETMAP_TXRING(nifp, i);
        if (nm_ring_empty(txring))
            continue;

        _sent = send_data(txring, data(), frag_size, data.size());
        sent += _sent;
    }

    /* 刷新所有剩余的数据包 */
    if (txring != NULL) {
        ioctl(pfd.fd, NIOCTXSYNC, NULL);
    }
    return 0;
}

int NetMapSocket::RecvData(DataCache& data, int timeout)
{
    struct pollfd pfd = { .fd = m_nmd->fd, .events = POLLIN };
    struct netmap_ring* rxring = NULL;
    struct netmap_if* nifp = NULL;
    int ret = 0;

#ifdef BUSYWAIT
    if (ioctl(pfd.fd, NIOCRXSYNC, NULL) < 0) {
        return;
    }
#else /* !BUSYWAIT */
    if (poll(&pfd, 1, timeout) <= 0) {
        return -2;
    }

    if (pfd.revents & POLLERR) {
        return -1;
    }
#endif /* !BUSYWAIT */

    nifp = m_nmd->nifp;

    for (int i = m_nmd->first_rx_ring; i <= m_nmd->last_rx_ring; i++) {

        rxring = NETMAP_RXRING(nifp, i);
        if (nm_ring_empty(rxring))
            continue;

        ret = recv_data(rxring, data);
    }
    return ret;
}

int NetMapSocket::send_data(struct netmap_ring* ring, void* frame, int per_frame, int all_size)
{
    u_int n, sent, tosend = all_size, head = ring->head;
    u_int frags = all_size / per_frame + (all_size % per_frame != 0);
    u_int frag_size = per_frame;

    char* tempPtr = NULL, * framePtr = (char*)frame;

    struct netmap_slot* slot = &ring->slot[head];
    n = nm_ring_space(ring);

    for (sent = 0; sent < frags; sent++)
    {
        int buf_changed;

        slot = &ring->slot[head];
        tempPtr = NETMAP_BUF(ring, slot->buf_idx);
        buf_changed = slot->flags & NS_BUF_CHANGED;

        slot->flags = 0;
        if (frags == 1 && buf_changed)
        {
            memcpy(tempPtr, framePtr, all_size);
            sent++; continue;
        }

        for (u_int i = 0; i < frags - 1; i++, sent++) {
            memcpy(tempPtr, framePtr, frag_size);

            slot->len = frag_size;
            slot->flags = NS_MOREFRAG;

            tosend -= frag_size;
            framePtr += frag_size;
            head = nm_ring_next(ring, head);  /* 每个槽的下一个buffer段 */
            slot = &ring->slot[head];
            tempPtr = NETMAP_BUF(ring, slot->buf_idx);
        }
        sent++;

        slot->flags = 0;
        slot->len = tosend;
        memcpy(tempPtr, framePtr, tosend);
        head = nm_ring_next(ring, head);
    }

    if (sent)
    {
        slot->flags |= NS_REPORT;
        ring->head = ring->cur = head;
    }
    if (sent < frags)
    {
        ring->cur = ring->tail;
    }
    return (sent);
}

int NetMapSocket::recv_data(struct netmap_ring* ring, DataCache& data)
{
    u_int head = ring->head, n = nm_ring_space(ring);
    int tempLen = data.capacity(), curLen = 0;
    
    for (u_int rx = 0; rx < n; rx++) {
        struct netmap_slot* slot = &ring->slot[head];
        char* p = NETMAP_BUF(ring, slot->buf_idx);
    
        if (curLen + slot->len > tempLen)
        {
            LOGFMTD(
                "data is too long, capacity : %d, dst : %d", 
                data.capacity(), curLen + slot->len
            );
            return -1;
        }
        memcpy(data() + curLen, p, slot->len);
        curLen = curLen + slot->len;

        head = nm_ring_next(ring, head);

        if (!(slot->flags & NS_MOREFRAG))
        {
            data.set(curLen);
            curLen = 0; break;
        }
    }
    ring->head = ring->cur = head;

    return 0;
}